// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: leon, junxiao

#pragma once

#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <queue>

#include <glog/logging.h>
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/log.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/strategy/dex_strat/simple_arb_strat_config.pb.h"
#include "coin2/strategy/dex_strat/simple_amm_strat_config.pb.h"
#include "coin2/strategy/order_executor/agg_executor.h"
#include "coin2/strategy/dex_strat/dex_strat_utils.h"
#include "coin2/strategy/strategy.h"
#include "coin2/strategy/util/moving_ohlc.h"
#include "coin2/strategy/util/latency_util.h"

namespace coin2::strategy::dex_strat {

using coin::proto::OrderSide;
using coin2::strategy::AggressiveExecutorConfig;
using coin2::strategy::PassiveExecutorConfig;
using ::coin2::strategy::order_executor::AggOrderExecutor;
using ::coin2::app::impl::Driver;
using ::coin2::exchange::base::feed::FeedUpdate;
using ::coin2::exchange::base::market::MarketExchangeApi;
using ::coin2::exchange::base::order::IOrderSubsystem;
using ::coin2::exchange::base::order::OrderUpdate;
using ::coin2::exchange::base::symbology::IProductHolder;
using ::coin::proto::OrderEvent;
using ::coin2::strategy::util::MovingHigh;
using ::coin2::strategy::util::MovingLow;
using ::coin2::strategy::dex_strat::RefProductConvertDirection;
using LiquiditySpec = coin2::exchange::base::order::LiquiditySpec;

class SimpleAmmSubStrategy;

class SimpleAmmPricer {
 public:
  explicit SimpleAmmPricer(
    const IProduct* trade_product,
    const IProduct* main_ref_product,
    RefProductConvertDirection ref_convert_direction,
    const IProduct* convert_ref_product,
    const SimpleAmmParameterConfig& parameter_config
    );
  void onBookFeed(const std::string& product_key, const FeedUpdate& upd);
  void onProvideSucceeded(double provide_price, double provide_price_upper, double provide_price_lower, double provide_amount, double start_fee = 0.0);
  void onWithdrawSucceeded(double withdraw_price);
  LiquidityOrder GetLiqidityOperation();
  bool ReadyForProvide();
  bool ReadyForWithdraw();
  bool HasLpPosition();
  void AlterLpPositionExplicit(bool new_pos);
  LiquidityStatus GetLiquidityStatus();
  void PrintLpInfo();
 private:
  std::string trade_product_key_;
  std::string main_ref_key_;
  std::string convert_ref_key_;
  RefProductConvertDirection ref_product_convert_direction_{RefProductConvertDirection::NONE};
  std::optional<double> last_main_ref_midp_;
  std::optional<double> last_convert_ref_midp_;

  int64_t min_op_interval_;
  int64_t window_estimator_normal_record_cnt_;
  int64_t window_estimator_mid_record_cnt_;
  double window_estimator_range_multiplier_;
  double window_size_multiplier_;

  double provide_signal_bar_;
  double normal_withdraw_bias_record_cnt_;
  double mid_provide_bias_record_cnt_;
  double long_provide_bias_record_cnt_;

  double min_half_win_size_;
  double can_adjust_bar_;
  double accept_loss_factor_;
  double minus_bias_bar_;
  double plus_bias_bar_;
  double bias_bar_decay_max_size_;
  double avg_price_out_of_range_;
  double minus_bias_adjust_bar_;
  double plus_bias_adjust_bar_;

  std::string status_record_file_path_;

  double lp_fee_per_unit_;
  double lp_loss_adjust_factor_;

  double default_amount_;

  bool has_lp_position_;
  double cur_lp_enter_price_;
  double cur_lp_enter_amount_;
  double cur_lp_enter_base_amount_;
  double cur_lp_enter_quote_amount_;
  double cur_lp_price_lower_;
  double cur_lp_price_upper_;
  double cur_lp_fee_loss_multiplier_;
  int64_t cur_lp_in_cnt_;
  int64_t cur_lp_out_cnt_;
  double last_lp_out_percentage_;

  double cur_loss_per_unit_;
  double cur_lp_loss_;
  double total_lp_loss_;

  double cur_lp_fee_;
  double total_fee_;
  double total_net_profit_;

  int64_t uniswap_tick_cnt_;
  double cur_trade_midp_;
  int64_t cur_trade_book_ts_;
  int64_t last_op_ts_; 
  LiquidityOrder cur_lp_op_;

  std::unique_ptr<WindowEstimator> normal_win_estimator_;
  std::unique_ptr<WindowEstimator> mid_win_estimator_;
  std::unique_ptr<BiasCalculator> normal_withdraw_bias_calculator_;
  std::unique_ptr<BiasCalculator> mid_provide_bias_calculator_;
  std::unique_ptr<BiasCalculator> long_provide_bias_calculator_;

  void ResetLpData();
  void CalculateCurrentLoss(double cur_price);
  void CalculateCurrentFee(double cur_price);
  double CalculateCurrentAcceptWinSize(double cur_price);
  double AdjustAcceptWinSize(double accept_win_size, double bias, double cur_price);
  bool BiasRealWithdraw(double bias, double cur_price);
  void CalculateLiquidityOperation();
};

class SimpleAmmStrategy : public ::coin2::strategy::IStrategy {
 public:
  SimpleAmmStrategy(
      const std::experimental::filesystem::path& strat_json,
      bool dry_run,
      std::optional<int64_t> timestamp);
  void Init(::coin2::app::Driver* driver) override;
  void InitAuthKey(::coin2::app::Driver* driver);
  void InitReserveMap();
  void onBookFeed(const FeedUpdate& upd) override;
  void onAccountInfo(const OrderUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onOrderLog(const coin::proto::OrderGatewayLog& order_log) override;
  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) final;
 private:
  int64_t strategy_created_time_;
  SimpleAmmProductConfig config_;
  std::vector<std::unique_ptr<SimpleAmmSubStrategy>> simple_amm_sub_strats_;
  std::unordered_map<std::string, std::vector<SimpleAmmSubStrategy*>> feed_sub_map_;
  std::unordered_map<std::string, SimpleAmmSubStrategy*> order_sub_map_;
  std::unique_ptr<StrategyReporter2> strat_reporter_;
  std::unique_ptr<AuthKey> auth_key_;
};

class SimpleAmmSubStrategy : public ::coin2::strategy::IStrategy {
 public:
  using ::coin2::strategy::IStrategy::Init;
  using ::coin2::strategy::IStrategy::onBookFeed;
  SimpleAmmSubStrategy(
      SimpleAmmStrategy* parent,
      const SimpleAmmProductConfig& config,
      int64_t strategy_created_time,
      bool enable_telemetry,
      bool dry_run);
  MarketExchangeApi mea() { return MarketExchangeApi::FromString(config_.trade_mea()); }
  IOrderSubsystem* os() { return driver_->order()->GetUniqueSystem(mea()); }
  void Init(::coin2::app::Driver* driver,
            ::coin2::strategy::util::StrategyReporter2* reporter);
  const SimpleAmmProductConfig& GetConfig() const { return config_; }
  const IProduct* GetTradeProduct() const {return trade_product_.get(); }
  const IProduct* GetTradeProductForReporter() const {return trade_product_for_reporter_.get(); }
  const IProduct* GetMainRefProduct() const {return main_ref_product_.get(); }
  const IProduct* GetConvertRefProduct() const {return convert_ref_product_.get(); }
  AggOrderExecutor* GetMutableExecutor() { return executor_.get(); }
  void onAccountInfo(const OrderUpdate& upd) override;
  void onBookFeed(const std::string& product_key, const FeedUpdate& upd);
  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) final;

 protected:

  SimpleAmmStrategy* parent_{nullptr};
  SimpleAmmProductConfig config_;
  int64_t strategy_created_time_;
  std::unique_ptr<IProduct> trade_product_;
  std::unique_ptr<IProduct> trade_product_for_reporter_;
  std::unique_ptr<IProduct> main_ref_product_;
  std::unique_ptr<IProduct> convert_ref_product_;
  RefProductConvertDirection ref_product_convert_direction_{RefProductConvertDirection::NONE};
  std::unique_ptr<AuthKey> auth_key_;
  Driver* driver_;
  exchange::base::strategy_util::StrategyLogger* strat_logger_;
  std::unique_ptr<AggOrderExecutor> executor_;
  std::unique_ptr<SimpleAmmPricer> pricer_;
  bool enable_telemetry_{false};
  bool dry_run_{false};
  std::set<std::string> feed_keys_;
  std::set<std::string> ref_feed_keys_;
  const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr_{nullptr};

  double reserved_pos_;
  double lot_size_;
  double max_pos_;
  double min_pos_;

  int64_t min_op_interval_;
  int64_t window_estimator_normal_record_cnt_;
  int64_t window_estimator_mid_record_cnt_;
  double window_estimator_range_multiplier_;
  double window_size_multiplier_;

  double provide_signal_bar_;
  double normal_withdraw_bias_record_cnt_;
  double mid_provide_bias_record_cnt_;
  double long_provide_bias_record_cnt_;

  double min_half_win_size_;
  double can_adjust_bar_;
  double accept_loss_factor_;
  double minus_bias_bar_;
  double plus_bias_bar_;
  double bias_bar_decay_max_size_;
  double avg_price_out_of_range_;
  double minus_bias_adjust_bar_;
  double plus_bias_adjust_bar_;

  bool return_pos_after_withdraw_{false};
  double return_pos_diff_ratio_;
  double return_pos_valid_ratio_;

  std::string status_record_file_path_;

  double lp_fee_per_unit_;
  double lp_loss_adjust_factor_;

  int64_t trade_tick_cnt_;
  double current_pos_;
  bool pos_initialized_;
  bool is_pos_updating_;
  bool is_returning_pos_;
  int64_t total_nft_token_cnt_;

  double default_amount_;
  int64_t last_op_ts_;
  double op_provide_price_;
  double op_provide_price_upper_;
  double op_provide_price_lower_;
  double op_provide_amount_;
  double op_withdraw_price_;

  void WriteStatusToFile(const LiquidityStatus& liquidity_status);
  LiquidityStatus ReadStatusFromFile(double default_price);
  void ReturnPosAfterWithdraw(double price);
  void InitalizePosition(double cur_price);
  void UpdatePosition();
};

}  // namespace coin2::strategy::dex_strat
